using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    class AssignmentMux : Component 
    {

        System.Collections.ArrayList SelectionBits;

        System.Collections.ArrayList Inputs;
        System.Collections.ArrayList InputSelectors;

        // Mux oMux; remove warning
        //Encoder oEncoder;

        NodeVector nvDefault;
        
        ClockComponent oClock = null;
        bool bRisingEdge=true;
        Node ndEnable = null;
        NodeVector nvResetTo;
        NodeVector nvResetIn;

        public NodeVector OutputNodes;
       

        public AssignmentMux(Component poParentComponent, string psName, int iWidth, NodeVector pnvDefault)
            : base(poParentComponent,psName)
        {
            SelectionBits = new System.Collections.ArrayList();
            Inputs = new System.Collections.ArrayList();
            InputSelectors = new System.Collections.ArrayList();

            //Default value is in the 0 position
            nvDefault = pnvDefault;
            Inputs.Add(nvDefault);
            InputSelectors.Add(new System.Collections.ArrayList());

            OutputNodes = this.CreateNodeVector("DATA_O", iWidth);
        }

        public void RegisterOutput(ClockComponent poClock, bool pbRisingEdge)
        {
            RegisterOutput(poClock, pbRisingEdge, null, null);
        }

        public void RegisterOutput(ClockComponent poClock, bool pbRisingEdge, NodeVector pnvResetTo, NodeVector pnvResetIn)
        {
            RegisterOutput(poClock, pbRisingEdge, pnvResetTo, pnvResetIn, null);
        }

        public void RegisterOutput(ClockComponent poClock)
        {
            RegisterOutput(poClock, true, null, null);
        }

        public void RegisterOutput(ClockComponent poClock, bool pbRisingEdge, NodeVector pnvResetTo, NodeVector pnvResetIn, Node pndEnable)
        {
            oClock = poClock;
            bRisingEdge = pbRisingEdge;
            ndEnable = pndEnable;
            nvResetTo = pnvResetTo;
            nvResetIn = pnvResetIn;
        }

        /*public void AddDefault(NodeVector poInput)
        {
            if (nvDefault != null)
            {
                if (poInput.IsNodeVectorConnected(nvDefault))
                {
                    System.Diagnostics.Debugger.Break();
                    System.Diagnostics.Debug.Fail(poInput.HirarchicalName + " already has a default value of " + nvDefault.HirarchicalName);
                }
                else
                    return;
            }

            nvDefault = poInput;
        }*/

        public void AddAssignment(NodeVector poSelectionNode, NodeVector poInput)
        {
            System.Collections.ArrayList ThisInputSelectors;
            if (SelectionBits.Contains(poSelectionNode))
            {
                System.Diagnostics.Debugger.Break();
                System.Diagnostics.Debug.Fail(poSelectionNode.HirarchicalName + " is being added twice to the same assignment.");
            }

            SelectionBits.Add(poSelectionNode);

            bool bInputConnected = false;
            int iInput=0;
            for (int iIdx = 0; iIdx < InputSelectors.Count; iIdx++)
            {
                NodeVector oNodeVector = (NodeVector)Inputs[iIdx];
                if (oNodeVector.IsNodeVectorConnected(poInput))
                {
                    bInputConnected = true;
                    iInput = iIdx;
                    break;
                }
            }

            if (bInputConnected)
            {
                ThisInputSelectors = (System.Collections.ArrayList)InputSelectors[iInput];
                ThisInputSelectors.Add(poSelectionNode);
            }
            else
            {
                ThisInputSelectors = new System.Collections.ArrayList();
                ThisInputSelectors.Add(poSelectionNode);
                InputSelectors.Add(ThisInputSelectors);
                Inputs.Add(poInput);
            }
        }

        public override void GenerateStructure()
        {
            NodeVector nvOut = this.CreateNodeVector("out", OutputNodes.Nodes.Count);
            if (oClock != null)
            {
                if (nvResetTo == null)
                {
                    RegisterComponent oReg = new RegisterComponent(this, "out_reg", oClock, OutputNodes.Nodes.Count, bRisingEdge, (ndEnable != null));
                    if (ndEnable != null)
                        oReg.EnableNodes[0].Net.Connect(ndEnable.Net);
                    oReg.InputNodes.Connection = nvOut;
                    OutputNodes.Connection = oReg.OutputNodes;
                }
                else
                {
                    ResetReg oReg = new ResetReg(this, "out_reg", oClock, OutputNodes.Nodes.Count, bRisingEdge, (ndEnable != null),false);
                    if (ndEnable != null)
                        oReg.EnableNodes[0].Net.Connect(ndEnable.Net);
                    oReg.InputNodes.Connection = nvOut;
                    OutputNodes.Connection = oReg.OutputNodes;
                    oReg.ResetTo.Connection = nvResetTo;
                    oReg.ResetIn.Connection = nvResetIn;
                }
            }
            else
            {
                OutputNodes.Connection = nvOut;
            }
            if(Inputs.Count == 1)
            {
                if (nvDefault == null)
                {
                    System.Diagnostics.Debugger.Break();
                    System.Diagnostics.Debug.Fail(this.HirarchicalName + " has no inputs");
                }
                else
                {
                    //OutputNodes.ConnectNodeVector(nvDefault);
                    nvOut.ConnectNodeVector((NodeVector)Inputs[0]);
                    return;
                }
            }

            
            int iDefaultInput = 0;
            int iMuxWidth = Conversion.MinBitWidth(Inputs.Count - 1);

            PriorityEncoder oEncoder = new PriorityEncoder(this,"encoder",Inputs.Count,iMuxWidth);
            Mux oMux = new Mux(this, "mux", OutputNodes.Nodes.Count, Inputs.Count);
            oMux.SelectNodes.Connection = oEncoder.OutputNodes;

            for (int iInput = 0; iInput < Inputs.Count; iInput++)
            {
                if (iInput == iDefaultInput)
                {
                    //oDefaultGate.OutputNodes.ConnectSubset(oEncoder.InputNodes, iInput, iInput);
                    oMux.InputNodes[iInput].Connection = nvDefault.PadZero(this,OutputNodes.Nodes.Count);
                    oEncoder.InputNodes.ConnectSubset(0, 0, Operators.CONST(this,1,1), 0, 0);
                }
                else
                {
                    NodeVector nvInput = (NodeVector)Inputs[iInput];
                    oMux.InputNodes[iInput].Connection =  nvInput.PadZero(this,OutputNodes.Nodes.Count);
                    System.Collections.ArrayList ThisInputSelectors;
                    ThisInputSelectors = (System.Collections.ArrayList)InputSelectors[iInput];
                    NodeVector oSelect = new NodeVector(this, "sel_input_" + iInput.ToString(), 1);
                    oEncoder.InputNodes.ConnectSubset(iInput, iInput, oSelect, 0, 0);
                    //oSelect.ConnectSubset(oDefaultGate.InputNodes, iInput, iInput);
                    if (ThisInputSelectors.Count == 1)
                    {
                        NodeVector oSelector = (NodeVector)ThisInputSelectors[0];
                        oSelect.Connection = oSelector;
                    }
                    else
                    {
                        OrGate oSelectGate = new OrGate(this, "selector_" + iInput.ToString(), ThisInputSelectors.Count);
                        oSelect.Connection = oSelectGate.OutputNodes;
                        for (int iSelector = 0; iSelector < ThisInputSelectors.Count; iSelector++)
                        {
                            NodeVector oSelector = (NodeVector)ThisInputSelectors[iSelector];
                            oSelector.ConnectSubset(0,0,oSelectGate.InputNodes, iSelector, iSelector);
                        }
                    }
                }
            }

            //Utility.SourceExtraBits(this,oEncoder.InputNodes,0);
            nvOut.Connection = oMux.OutputNodes;
        }

        protected override Component TestBuildStructure(Component poTopComponent, string psTestName)
        {
            NodeVector nvSelA = poTopComponent.CreateNodeVector("sel_a", 1,NodeFlowType.Source);
            NodeVector nvSelB = poTopComponent.CreateNodeVector("sel_b", 1, NodeFlowType.Source);
            NodeVector nvSelC = poTopComponent.CreateNodeVector("sel_c", 1, NodeFlowType.Source);
            NodeVector nvSelD = poTopComponent.CreateNodeVector("sel_d", 1, NodeFlowType.Source);
            NodeVector nvPassB = poTopComponent.CreateNodeVector("pass_b", 2);

            Constant cA = new Constant(poTopComponent,"c_a","001");
            Constant cB = new Constant(poTopComponent,"c_b","11");
            nvPassB.Connection = cB.OutputNodes;

            Constant cDef = new Constant(poTopComponent,"c_def","101");

            AssignmentMux oaMux = new AssignmentMux(poTopComponent, "testAssign", 3,cDef.OutputNodes);

            oaMux.AddAssignment(nvSelD, cDef.OutputNodes);
            oaMux.AddAssignment(nvSelC, nvPassB);
            oaMux.AddAssignment(nvSelB, cB.OutputNodes);
            oaMux.AddAssignment(nvSelA, cA.OutputNodes);

            return oaMux;
        }

        protected override string TestComponent(Clock poClock, Component poTestComponent, string psTestName)
        {
            string sTestId;

            poTestComponent.SimulationStabilizeComponent();

            AssignmentMux oaMux = (AssignmentMux)poTestComponent;

            NodeVector nvSelA = poTestComponent.ParentComponent.GetNodeVector(".sel_a");
            NodeVector nvSelB = poTestComponent.ParentComponent.GetNodeVector(".sel_b");
            NodeVector nvSelC = poTestComponent.ParentComponent.GetNodeVector(".sel_c");
            NodeVector nvSelD = poTestComponent.ParentComponent.GetNodeVector(".sel_d");

            string sIn = "1000";
            string sOut = "001";

            PriorityEncoder oEncoder = (PriorityEncoder)oaMux.GetComponent("encoder");
            OrGate oSelectGate = (OrGate)oaMux.GetComponent("selector_1");
            Mux oMux = (Mux)oaMux.GetComponent("mux");
            Constant cA = (Constant)poTestComponent.ParentComponent.GetComponent("c_a");
            
            sTestId = sIn + " -> " + sOut;
            nvSelA.SimulatedNodeVectorAsString = sIn[0].ToString();
            nvSelB.SimulatedNodeVectorAsString = sIn[1].ToString();
            nvSelC.SimulatedNodeVectorAsString = sIn[2].ToString();
            nvSelD.SimulatedNodeVectorAsString = sIn[3].ToString();
            //poClock.DoClock(1.0);
            Console.WriteLine("Encoder " + oEncoder.InputNodes.ToString() + " -> " + oEncoder.OutputNodes.ToString());
            Console.WriteLine("Or Gate " + oSelectGate.InputNodes.ToString() + " -> " + oSelectGate.OutputNodes.ToString());
            Console.WriteLine("Mux 0:" + oMux.InputNodes[0].ToString() + " 1:" + oMux.InputNodes[1].ToString() + " 2:" + oMux.InputNodes[2].ToString() + " Sel:" + oMux.SelectNodes.ToString() + " -> " + oMux.OutputNodes.ToString());
            Console.WriteLine("Constant A -> " + cA.OutputNodes.ToString());
            if (oaMux.OutputNodes.NodeVectorAsString != sOut)
                return sTestId + " GOT " + oaMux.OutputNodes.NodeVectorAsString;

            sIn = "1101";
            sOut = "001";
            sTestId = sIn + " -> " + sOut;
            nvSelA.SimulatedNodeVectorAsString = sIn[0].ToString();
            nvSelB.SimulatedNodeVectorAsString = sIn[1].ToString();
            nvSelC.SimulatedNodeVectorAsString = sIn[2].ToString();
            nvSelD.SimulatedNodeVectorAsString = sIn[3].ToString();
            if (oaMux.OutputNodes.NodeVectorAsString != sOut)
                return sTestId + " GOT " + oaMux.OutputNodes.NodeVectorAsString;

            sIn = "0010";
            sOut = "011";
            sTestId = sIn + " -> " + sOut;
            nvSelA.SimulatedNodeVectorAsString = sIn[0].ToString();
            nvSelB.SimulatedNodeVectorAsString = sIn[1].ToString();
            nvSelC.SimulatedNodeVectorAsString = sIn[2].ToString();
            nvSelD.SimulatedNodeVectorAsString = sIn[3].ToString();
            Console.WriteLine("Encoder " + oEncoder.InputNodes.ToString() + " -> " + oEncoder.OutputNodes.ToString());
            Console.WriteLine("Or Gate " + oSelectGate.InputNodes.ToString() + " -> " + oSelectGate.OutputNodes.ToString());
            Console.WriteLine("Mux 0:" + oMux.InputNodes[0].ToString() + " 1:" + oMux.InputNodes[1].ToString() + " 2:" + oMux.InputNodes[2].ToString() + " Sel:" + oMux.SelectNodes.ToString() + " -> " + oMux.OutputNodes.ToString());
            Console.WriteLine("Constant A -> " + cA.OutputNodes.ToString());

            if (oaMux.OutputNodes.NodeVectorAsString != sOut)
                return sTestId + " GOT " + oaMux.OutputNodes.NodeVectorAsString;

            sIn = "0100";
            sOut = "011";
            sTestId = sIn + " -> " + sOut;
            nvSelA.SimulatedNodeVectorAsString = sIn[0].ToString();
            nvSelB.SimulatedNodeVectorAsString = sIn[1].ToString();
            nvSelC.SimulatedNodeVectorAsString = sIn[2].ToString();
            nvSelD.SimulatedNodeVectorAsString = sIn[3].ToString();
            if (oaMux.OutputNodes.NodeVectorAsString != sOut)
                return sTestId + " GOT " + oaMux.OutputNodes.NodeVectorAsString;

            sIn = "0000";
            sOut = "101";
            sTestId = sIn + " -> " + sOut;
            nvSelA.SimulatedNodeVectorAsString = sIn[0].ToString();
            nvSelB.SimulatedNodeVectorAsString = sIn[1].ToString();
            nvSelC.SimulatedNodeVectorAsString = sIn[2].ToString();
            nvSelD.SimulatedNodeVectorAsString = sIn[3].ToString();
            if (oaMux.OutputNodes.NodeVectorAsString != sOut)
                return sTestId + " GOT " + oaMux.OutputNodes.NodeVectorAsString;

            sIn = "0001";
            sOut = "101";
            sTestId = sIn + " -> " + sOut;
            nvSelA.SimulatedNodeVectorAsString = sIn[0].ToString();
            nvSelB.SimulatedNodeVectorAsString = sIn[1].ToString();
            nvSelC.SimulatedNodeVectorAsString = sIn[2].ToString();
            nvSelD.SimulatedNodeVectorAsString = sIn[3].ToString();
            if (oaMux.OutputNodes.NodeVectorAsString != sOut)
                return sTestId + " GOT " + oaMux.OutputNodes.NodeVectorAsString;


            return "";
        }
    }
}
